总概
在做公司项目过程中遇到一些需要进行替换原来方法情况。小编对 Objc
在消息发送和转发过程整理,针对在消息发送和转发在 runtime
层实现和提出的方法替换方案原理做出详细说明和记录。
下面是小编本博文的写作思路:
iOS 消息发送和转发过程
想要对现有的 Method
进行替换找到比较可行的方案,其一可以从方法在执行过程中考虑。下面小编将整理的消息发送和转发的详细步骤进行整理。
Objc 的动态特性
Objc
是基于 C
和 Smalltalk
为基础实现面向对象开发的动态语言,Objc
具有特色一点就是采纳了 Smalltalk
语言消息的特性,所以我们在 Objc
中经常使用 [receiver message]
来进行消息传递。Objc
的动态语言的特性可以根据 NSObject
方式之一来实现交互,下面看相关 API
(下面均可进行动态判断):
1 | + (NSString *)description;//需要重载并为定义的类进行描述 |
iOS 消息发送转发机制
在 Objc
使用 Foundation 中 NSObject
中实现动态特性中,上述列出消息发送和转发过程的调用的 API
。在整体消息从发送转发可以分为以下三个时段:
- 动态解析
- 快速转发
- 慢速转发
下图是消息发送和转发过程三个时段流程,后面给出详细说明:
下面是在 iOS 方法调用实现:
1 | //未实现方法调用 |
动态解析
如果要实现动态解析就要使用下面的方法:
1 | + (BOOL)resolveClassMethod:(SEL)sel;//解析类方法选择子 |
当我们在实现 [receiver message]
在没有相关实现的情况下,系统就会沿着 resolveInstanceMethod:
顺着继承向上查找相关 message
实现方法,如果没有找到相关实现就会返回 NO
进行下一步操作。在这个过程中我们可以通过动态加载的方式,实现我们预先实现的方法。
动态加载实现代码如下:
1 | + (BOOL)resolveInstanceMethod:(SEL)sel { |
从打印的结果来看:我们通过动态加载的方式实现预先方法的调用。但是在打印中 SEL
还是原来的选择子,后面会讲解。
从上面的解决方式实现来看,严格意义上来说动态解析不是消息的转发。因为在指向过程中消息的接受的对象还是
receiver
, 只是把指向message
的IMP
的指针加入到receiver
的方法列表。
快速转发
快速转发相当于我们在请求网页是重新定向。receiver
对象和其继承父类没有找到相应的实现方法,而把实现的转向其他的对象。
快速转发实现代码如下:
1 | //重定向的实现类 |
更具上面打印的结果:看出快速转发可以实现当前
receiver
方法列表找不到相关的message
的IMP
,可以通过制定其他的对象来进行实现。
慢速转发
慢速转发过程中使用两个对象:NSMethodSignature
和 NSInvocation
NSMethodSignature
: 对象定义了方法的签名,签名中包含:方法的参数和返回值。NSInvocation
: 保存了目标、选着子和参数等消息调用的必须的全部元素。
在转发过程中要是到到下面 API
:
1 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;//在协议中实现,返回相关的方法签名 |
下面先直接把慢速转发的代码列出来:
1 | //重定向的实现类 |
快速转发失败后,运行时系统先调用
methodSignatureForSelector:
返回一个方法签名用于创建NSInvocation
对象,创建好后调用forwardInvocation:
方法,在改方法中NSInvocation
对象将调用invokeWithTarget:
方法唤醒新接受者中的同名方法。
iOS 发送和转发小结
在消息发送和转发过程中没有发现实现 message
的 IMP
过程中三步骤中均可以实现相关的消息添加,也可以在三者中进行相关的方法替换。
但是三者之间还是有各自的特点:
- 动态解析 : 适用于将原来的类中的方法替换掉或者延迟加载。
- 快速转发 : 可以将消息处理转发给其他对象,使用范围更广,不只是限于原来的对象。
- 慢速转发 : 跟快转发一样可以消息转发,但它能通过NSInvocation对象获取更多消息发送的信息,例如:target、selector、arguments和返回值等信息。
iOS 消息 runtime 层实现过程
小编在 iOS 消息发送和转发根据 NSObject
方面讲述 [receiver message]
消息调用处理,但是在 runtime
层在怎样情形呢?
[receiver message]
这句的含义是:向 receiver
发送名为 message
的消息。
打开开发文档(在 objc/message.h
中)
1 | [receiver message]; |
是下面的表示方式:
1 | //objc_msgSend(self, Selector);//基本表示方法 |
其中 objc_msgSend
的真正实现代码如下:
1 | objc_msgSend(id _Nullable self, SEL _Nonnull op, ...); |
小编下面简单的介绍下
objc_msgSend
中的参数和如何使用runtime
实现方法替换。
id
上面在 objc_msgSend
中的第一参数 id
。 小编认为对于 id
用该不会陌生,id
是指向类实例一个指针。
1 | typedef struct objc_object *id; |
Objc_object
中包含一个 isa
的指针, isa
是 isa_t
的联合体。根据 isa
指针可以获取对象所属的类。
SEL
objc_msgSend
中的第二个参数 SEL
,在 Objc
中变现为 selector
。selector
是方法的选择器,可以视作系统用来区分方法的 ID
,而此 ID
的类型为 SEL
结构。
1 | typedef struct objc_selector *SEL; |
最本质的就是 SEL
就是映射方法的字符串,小编记得可以在 objc
中 @selector()
和 runtime
中的 objc_registerName
来获取方法选着器。
注:在不同类中方法名相同的
SEL
相同,在同一个类中的方法名相同参数不同的SEL
相同。
动态实现方法替换
1 | - (BOOL)swapMethods { |
上面是使用 runtime
的形式用新的方法替换掉原来的方法,其中使用到的知识会在 runtime
详解中解析讲解。
提出替换方案
在替换方法方案中,目前有 4 种:
- 在消息动态解析中替换方式 :动态解析替换的基础是没有找到实现文件,需要对实现进行屏蔽
- 使用
runtime
实现动态加载替换方式 :需要修改原来方法的名字或者修改参数 - 使用子类继承的方式 :不需要修改任何信息,但是如果在有子类情况下改动比较麻烦
- 使用
Category
方式 :不需要改动名字和参数,但是会引入新的文件数目
小结
在替换方法过程中要根据具体的情景来进行选择合适的替换方式。
参考资料:
Message forwarding
Objective-c-messaging
The faster objc_msgSend
Understanding objective-c runtime
objc4/objc4-709
objc-private.h